当前位置:  开发笔记 > 编程语言 > 正文

`std :: shared_ptr`的自动循环断路器的可行性

如何解决《`std::shared_ptr`的自动循环断路器的可行性》经验,为你挑选了2个好方法。

C++ 11引入了引用计数的智能指针std::shared_ptr.作为引用计数,这些指针无法自动回收循环数据结构.但是,可以自动收集参考周期,例如Python和PHP.为了将此技术与垃圾收集区分开来,问题的其余部分将其称为循环中断.

鉴于似乎没有为C++添加等效功能的建议,是否有一个根本原因,为什么类似于已经部署在其他语言中的循环断路器不适用std::shared_ptr

请注意,这个问题不能归结为"为什么没有GC for C++",这已经被问过了.C++ GC通常是指一个自动管理所有动态分配对象的系统,通常使用某种形式的Boehm保守收集器实现.有人指出,这样的收集器不适合RAII.由于垃圾收集器主要管理内存,甚至可能在内存不足之前就被调用,并且C++析构函数管理其他资源,依赖于GC来运行析构函数会在最坏情况下引入非确定性和资源不足.它还指出,在存在更明确和可预测的智能指针的情况下,完全不需要GC.

但是,基于库的智能指针循环断路器(类似于引用计数解释器使用的循环断路器)与通用GC有重要区别:

它只关心通过管理的对象shared_ptr.这些对象已经参与共享所有权,因此必须处理延迟的析构函数调用,其确切的时间取决于所有权结构.

由于其范围有限,循环断路器不关心破坏或减慢Boehm GC的模式,例如指针掩蔽或包含偶尔指针的巨大不透明堆块.

它可以选择加入,比如std::enable_shared_from_this.不使用它的对象不必为控制块中的额外空间付费以保存循环断路器元数据.

循环断路器不需要全面的"根"对象列表,这在C++中很难获得.与标记扫描GC不同,它会查找所有活动对象并丢弃其余对象,而循环断路器仅遍历可形成循环的对象.在现有实现中,类型需要以函数的形式提供帮助,该函数枚举对可以参与循环的其他对象的引用(直接或间接).

它依赖于常规的"当引用计数降至零时破坏"语义来破坏循环垃圾.一旦确定了一个循环,就会要求参与其中的对象清除其强大的引用,例如通过调用reset().这足以打破循环并自动销毁对象.要求对象提供并清除其强力引用(根据要求),确保循环断路器不会破坏封装.

缺乏自动循环中断的建议表明该想法因实际或哲学原因而被拒绝.我很好奇原因是什么.为了完整起见,这里有一些可能的反对意见:

"它会引入循环shared_ptr对象的非确定性破坏." 如果程序员控制了循环断路器的调用,那么它就不是非确定性的.此外,一旦被调用,循环断路器的行为将是可预测的 - 它将破坏所有当前已知的循环.这类似于shared_ptr析构函数在引用计数降至零后如何销毁基础对象,尽管这可能会导致"非确定性"级联的进一步破坏.

"就像任何其他形式的垃圾收集一样,循环断路器会在程序执行中引入暂停." 实现此功能的运行时的经验表明,暂停是最小的,因为GC只处理循环垃圾,所有其他对象都通过引用计数回收.如果永远不会自动调用循环检测器,则循环断路器的"暂停"可能是运行它的可预测结果,类似于破坏大型std::vector运行大量析构函数的方法.(在Python中,循环gc是自动运行的,但有一些API可以在不需要它的代码段暂时禁用它.稍后重新启用GC将获取同时创建的所有循环垃圾.)

"循环断路器是不必要的,因为循环不是那么频繁,可以很容易地避免使用std::weak_ptr." 事实上,循环很容易在许多简单的数据结构中出现 - 例如,一个树,其中孩子有一个指向父项的后向指针,或一个双向链表.在某些情况下,复杂系统中的异质对象之间的循环仅偶尔与某些数据模式形成,并且难以预测和避免.在某些情况下,用弱变量替换哪个指针远非明显.

Nicol Bolas.. 13

这里有很多问题需要讨论,所以我重写了我的帖子以更好地压缩这些信息.

自动循环检测

你的想法是拥有一个circle_ptr智能指针(我知道你想要添加它shared_ptr,但是更容易谈论一个新类型来比较两者).这个想法是,如果智能指针绑定的类型派生自某些类型cycle_detector_mixin,则激活自动循环检测.

这个mixin还要求该类型实现一个接口.它必须能够枚举该circle_ptr实例直接拥有的所有实例.它必须提供使其中一个无效的手段.

我认为这是一个非常不切实际的解决方案.它过于脆弱,需要用户进行大量的手工操作.因此,它不适合包含在标准库中.以下是一些原因.

决定论和成本

"它会引入循环shared_ptr对象的非确定性破坏." 循环检测仅在shared_ptr参考计数降至零时发生,因此程序员可以控制何时发生.因此,它不是非确定性的.它的行为是可预测的 - 它将从该指针中销毁所有当前已知的循环.这类似于shared_ptr析构函数在引用计数降至零后如何销毁基础对象,尽管这可能会导致"非确定性"级联的进一步破坏.

这是事实,但不是一种有用的方式.

常规shared_ptr破坏的决定论与你所建议的决定论之间存在很大差异.即:shared_ptr便宜.

shared_ptr析构函数执行原子递减,然后进行条件测试以查看值是否递减为零.如果是这样,则调用析构函数并释放内存.而已.

你的建议使这更复杂.最糟糕的情况是,每次circle_ptr销毁时,代码都必须遍历数据结构以确定是否存在循环.大多数情况下,周期不会存在.但它仍然需要寻找它们,只是为了确保.它必须每次摧毁一个都这样做circle_ptr.

Python et.人.解决这个问题因为它们内置于语言中.他们能够看到正在发生的一切.因此,他们可以检测何时在进行这些分配时指定指针.通过这种方式,这样的系统不断地进行少量工作以建立循环链.一旦参考消失,它可以查看其数据结构并采取行动,如果它创建一个循环链.

但你所建议的是图书馆功能,而不是语言功能.并且库类型不能真正做到这一点.或者更确切地说,他们可以,但只有帮助.

记住:一个实例circle_ptr无法知道它所属的子对象.它不能自动将指向自身的指针转换为指向其拥有类的指针.如果没有这种能力,cycle_detector_mixin如果重新分配,它就无法更新拥有它的数据结构.

现在,它可以手动执行此操作,但只能在其拥有的实例的帮助下完成.这意味着circle_ptr需要一组构造函数,这些构造函数被赋予一个指向其拥有实例的指针,该实例派生自cycle_detector_mixin.然后,operator=它将能够告知其所有者它已被更新.显然,复制/移动赋值不会复制/移动拥有的实例指针.

当然,这需要拥有实例为其创建的circle_ptr一个提供指向自身的指针.在每个创建circle_ptr实例的构造函数和函数中.在其自身及其拥有的任何类别中,这些类别也不受管理cycle_detection_mixin.没有失败.这在系统中造成了一定程度的脆弱性; 必须为circle_ptr类型所拥有的每个实例花费手动工作.

这还需要circle_ptr包含3种指针类型:指向您获取的对象operator*的指针,指向实际托管存储的指针以及指向该实例所有者的指针.实例必须包含指向其所有者的指针的原因是它是每个实例数据,而不是与块本身相关的信息.它的实例circle_ptr需要能够在反弹时告诉其所有者,因此实例需要该数据.

这必须是静态开销.您无法知道circle_ptr实例何时属于另一种类型,何时不属于另一种类型.因此circle_ptr,即使是那些不使用循环检测功能的人,也必须承担这3个指针的成本.

因此,这不仅需要很大程度的脆弱性,而且价格昂贵,使该类型的尺寸膨胀50%.shared_ptr用这种类型替换(或者说更重要的是,增加shared_ptr这个功能)是不可行的.

从好的方面来说,您不再需要派生的用户cycle_detector_mixin来实现获取circle_ptr实例列表的方法.相反,您可以使用circle_ptr实例注册自己的类.这允许circle_ptr可以循环的实例直接与其拥有对话cycle_detector_mixin.

所以有一些东西.

封装和不变量

能够告诉类使其中一个circle_ptr对象无效的需要从根本上改变了类与其任何circle_ptr成员交互的方式.

不变量是一段代码假定为某种状态,因为它在逻辑上不可能是假的.如果检查const int变量是否> 0,那么您已为以后的代码建立了一个不变量,该值为正值.

存在封装以允许您能够在类中构建不变量.仅构造函数不能这样做,因为外部代码可以修改类存储的任何值.封装允许您阻止外部代码进行此类修改.因此,您可以为类存储的各种数据开发不变量.

这就是封装是.

使用a shared_ptr,可以围绕这种指针的存在构建一个不变量.您可以设计您的类,使指针永远不为null.因此,没有人必须检查它是否为空.

事实并非如此circle_ptr.如果您实现了cycle_detector_mixin,那么您的代码必须能够处理任何这些circle_ptr实例变为null的情况.因此,析构函数不能假设它们是有效的,析构函数调用的任何代码也不能作出这样的假设.

因此,您的类无法与指向的对象建立不变量circle_ptr.至少,如果它是cycle_detector_mixin其相关注册和诸如此类的东西的一部分.

您可以争辩说,您的设计在技术上不会破坏封装,因为circle_ptr实例仍然可以是私有的.但是这个班级愿意放弃对循环检测系统的封装.因此,班级不再能够确保某些种类的不变量.

这听起来像打破我的封装.

线程安全

为了访问a weak_ptr,用户必须lock它.这将返回一个shared_ptr,确保对象保持活动状态(如果它仍然存在).锁定是一种原子操作,就像参考递增/递减一样.所以这都是线程安全的.

circle_ptrs可能不是非常安全的.circle_ptr如果另一个线程释放了对它的最后一个非循环引用,则a可能从另一个线程变为无效.

我对此并不完全确定.可能只有在您已经对对象的销毁进行数据竞争或使用非拥有引用时才会出现这种情况.但我不确定您的设计是否可以线程安全.

毒力因素

这个想法非常具有病毒性.可能发生循环引用的每种其他类型都必须实现此接口.这不是你可以放在一种类型上的东西.为了获得好处,每种可以参与循环参考的类型都必须使用它.始终如一.

如果你试图circle_ptr要求它管理的对象实现cycle_detector_mixin,那么你就不可能使用任何其他类型的指针.它不会取代(或增加)shared_ptr.因此,编译器无法帮助检测意外误用.

当然,make_shared_from_this编译器无法检测到意外误用.然而,这不是病毒构建体.因此,对于需要此功能的人来说,这只是一个问题.相比之下,获得收益的唯一方法cycle_detector_mixin是尽可能全面地使用它.

同样重要的是,因为这个想法是如此具有病毒性,你将会使用它.因此,您比用户更容易遇到多重继承问题make_shared_from_this.这不是一个小问题.特别是因为cycle_detector_mixin可能会static_cast用来访问派生类,所以你将无法使用虚拟继承.

合计

因此,为了检测周期,你必须做的就是你必须做的事情,编译器将不会验证这些周期:

    参与一个周期的每个班级都必须来自cycle_detector_mixin.

    任何时候,cycle_detector_mixin派生的类circle_ptr在其自身内构造一个实例(直接或间接,但不是在自己派生的类中cycle_detector_mixin),将指针传递给自己cycle_ptr.

    不要假设cycle_ptr类的任何子对象是有效的.由于线程问题,可能甚至在成员函数中变得无效的程度.

这是成本:

    循环检测数据结构cycle_detector_mixin.

    每个都cycle_ptr必须大50%,甚至是不用于循环检测的那些.

对所有权的误解

最终,我认为这个想法归结为对shared_ptr实际情况的误解.

"循环探测器是不必要的,因为循环不是那么频繁,可以很容易地避免使用std::weak_ptr." 事实上,循环很容易在许多简单的数据结构中出现 - 例如,一个树,其中孩子有一个指向父项的后向指针,或一个双向链表.在某些情况下,复杂系统中的异质对象之间的循环仅偶尔与某些数据模式形成,并且难以预测和避免.在某些情况下,用弱变量替换哪个指针远非明显.

这是通用GC的一个非常常见的论点.这个论点的问题在于它通常会假设智能指针的使用无效.

使用shared_ptr 手段的东西.如果类存储a shared_ptr,则表示该类具有该对象的所有权.

所以解释一下:为什么链表中的节点需要拥有下一个节点和前一个节点?为什么树中的子节点需要拥有其父节点?哦,他们需要能够引用其他节点.但他们不需要控制它们的寿命.

例如,我将树节点实现为子节点的数组unique_ptr,并使用指向父节点的单个指针.一个普通的指针,而不是一个智能指针.毕竟,如果树构造正确,父节点将拥有其子节点.因此,如果存在子节点,则它的父节点必须存在 ; 没有有效的父母,孩子就不能存在.

使用双链表,我可能左指针是a unique_ptr,右边是常规指针.或相反亦然; 一种方式并不比另一种更好.

你的心态似乎是我们应该永远使用shared_ptr的东西,让自动系统解决问题的处理方法.无论是循环引用还是其他任何东西,只需让系统搞清楚即可.

这不是shared_ptr为了什么.智能指针的目标并不是你不再考虑所有权; 这是你可以直接在代码中表达所有权关系.

总体

与使用weak_ptr打破周期相比,这有何改进?您现在无处不在,无需识别周期可能发生的时间并进行额外的工作.工作非常脆弱; 如果你做错了,你就不会错过你错过了一个应该用过的地方weak_ptr.只有它更糟糕,因为您可能认为您的代码是安全的.

安全的假象比没有安全更糟糕.至少后者会让你小心.

你能实现这样的东西吗?有可能.它是标准库的合适类型吗?不,它太脆弱了.您必须始终以任何方式正确地实施它,在任何可能出现循环的地方......或者您什么也得不到.

权威参考

对于从未提出,建议或甚至想象用于标准化的事物,可能没有权威参考.升压有没有这样的类型,这样的结构被从未考虑过的boost::shared_ptr.即便是第一个智能指针纸(PDF)也从未考虑过这种可能性.shared_ptr通过一些手动努力扩展到自动处理周期的主题从未在标准提案论坛上进行过讨论,其中已经考虑了很多愚蠢的想法.

我可以提供的最接近参考的是1994年的这篇论文,关于引用计数的智能指针.本文主要谈论使相当于shared_ptrweak_ptr部分语言的(这是在早期的日子,他们甚至不认为这是可以写一个shared_ptr,允许铸造shared_ptrshared_ptr何时U是一个基础T).但即便如此,它还是明确表示不会收集周期.它没有花太多时间在为什么不,但它确实说明了这一点:

但是,具有清理功能的收集对象的循环是有问题的.如果A和B彼此可达,则先破坏任何一个将违反排序保证,留下悬空指针.如果收集器任意中断了循环,程序员将没有真正的排序保证,并且可能导致细微的,与时间相关的错误.到目前为止,还没有人设计出一个安全,通用的解决方案[Hayes 92].

这本质上是我指出的封装/不变问题:使类型的指针成员无效会破坏不变量.

所以基本上,很少有人甚至考虑过这种可能性,那些很快就把它丢弃的人很不切实际.如果你真的相信它们是错的,证明它的唯一最佳方法就是自己实施它.然后提出它用于标准化.



1> Nicol Bolas..:

这里有很多问题需要讨论,所以我重写了我的帖子以更好地压缩这些信息.

自动循环检测

你的想法是拥有一个circle_ptr智能指针(我知道你想要添加它shared_ptr,但是更容易谈论一个新类型来比较两者).这个想法是,如果智能指针绑定的类型派生自某些类型cycle_detector_mixin,则激活自动循环检测.

这个mixin还要求该类型实现一个接口.它必须能够枚举该circle_ptr实例直接拥有的所有实例.它必须提供使其中一个无效的手段.

我认为这是一个非常不切实际的解决方案.它过于脆弱,需要用户进行大量的手工操作.因此,它不适合包含在标准库中.以下是一些原因.

决定论和成本

"它会引入循环shared_ptr对象的非确定性破坏." 循环检测仅在shared_ptr参考计数降至零时发生,因此程序员可以控制何时发生.因此,它不是非确定性的.它的行为是可预测的 - 它将从该指针中销毁所有当前已知的循环.这类似于shared_ptr析构函数在引用计数降至零后如何销毁基础对象,尽管这可能会导致"非确定性"级联的进一步破坏.

这是事实,但不是一种有用的方式.

常规shared_ptr破坏的决定论与你所建议的决定论之间存在很大差异.即:shared_ptr便宜.

shared_ptr析构函数执行原子递减,然后进行条件测试以查看值是否递减为零.如果是这样,则调用析构函数并释放内存.而已.

你的建议使这更复杂.最糟糕的情况是,每次circle_ptr销毁时,代码都必须遍历数据结构以确定是否存在循环.大多数情况下,周期不会存在.但它仍然需要寻找它们,只是为了确保.它必须每次摧毁一个都这样做circle_ptr.

Python et.人.解决这个问题因为它们内置于语言中.他们能够看到正在发生的一切.因此,他们可以检测何时在进行这些分配时指定指针.通过这种方式,这样的系统不断地进行少量工作以建立循环链.一旦参考消失,它可以查看其数据结构并采取行动,如果它创建一个循环链.

但你所建议的是图书馆功能,而不是语言功能.并且库类型不能真正做到这一点.或者更确切地说,他们可以,但只有帮助.

记住:一个实例circle_ptr无法知道它所属的子对象.它不能自动将指向自身的指针转换为指向其拥有类的指针.如果没有这种能力,cycle_detector_mixin如果重新分配,它就无法更新拥有它的数据结构.

现在,它可以手动执行此操作,但只能在其拥有的实例的帮助下完成.这意味着circle_ptr需要一组构造函数,这些构造函数被赋予一个指向其拥有实例的指针,该实例派生自cycle_detector_mixin.然后,operator=它将能够告知其所有者它已被更新.显然,复制/移动赋值不会复制/移动拥有的实例指针.

当然,这需要拥有实例为其创建的circle_ptr一个提供指向自身的指针.在每个创建circle_ptr实例的构造函数和函数中.在其自身及其拥有的任何类别中,这些类别也不受管理cycle_detection_mixin.没有失败.这在系统中造成了一定程度的脆弱性; 必须为circle_ptr类型所拥有的每个实例花费手动工作.

这还需要circle_ptr包含3种指针类型:指向您获取的对象operator*的指针,指向实际托管存储的指针以及指向该实例所有者的指针.实例必须包含指向其所有者的指针的原因是它是每个实例数据,而不是与块本身相关的信息.它的实例circle_ptr需要能够在反弹时告诉其所有者,因此实例需要该数据.

这必须是静态开销.您无法知道circle_ptr实例何时属于另一种类型,何时不属于另一种类型.因此circle_ptr,即使是那些不使用循环检测功能的人,也必须承担这3个指针的成本.

因此,这不仅需要很大程度的脆弱性,而且价格昂贵,使该类型的尺寸膨胀50%.shared_ptr用这种类型替换(或者说更重要的是,增加shared_ptr这个功能)是不可行的.

从好的方面来说,您不再需要派生的用户cycle_detector_mixin来实现获取circle_ptr实例列表的方法.相反,您可以使用circle_ptr实例注册自己的类.这允许circle_ptr可以循环的实例直接与其拥有对话cycle_detector_mixin.

所以有一些东西.

封装和不变量

能够告诉类使其中一个circle_ptr对象无效的需要从根本上改变了类与其任何circle_ptr成员交互的方式.

不变量是一段代码假定为某种状态,因为它在逻辑上不可能是假的.如果检查const int变量是否> 0,那么您已为以后的代码建立了一个不变量,该值为正值.

存在封装以允许您能够在类中构建不变量.仅构造函数不能这样做,因为外部代码可以修改类存储的任何值.封装允许您阻止外部代码进行此类修改.因此,您可以为类存储的各种数据开发不变量.

这就是封装是.

使用a shared_ptr,可以围绕这种指针的存在构建一个不变量.您可以设计您的类,使指针永远不为null.因此,没有人必须检查它是否为空.

事实并非如此circle_ptr.如果您实现了cycle_detector_mixin,那么您的代码必须能够处理任何这些circle_ptr实例变为null的情况.因此,析构函数不能假设它们是有效的,析构函数调用的任何代码也不能作出这样的假设.

因此,您的类无法与指向的对象建立不变量circle_ptr.至少,如果它是cycle_detector_mixin其相关注册和诸如此类的东西的一部分.

您可以争辩说,您的设计在技术上不会破坏封装,因为circle_ptr实例仍然可以是私有的.但是这个班级愿意放弃对循环检测系统的封装.因此,班级不再能够确保某些种类的不变量.

这听起来像打破我的封装.

线程安全

为了访问a weak_ptr,用户必须lock它.这将返回一个shared_ptr,确保对象保持活动状态(如果它仍然存在).锁定是一种原子操作,就像参考递增/递减一样.所以这都是线程安全的.

circle_ptrs可能不是非常安全的.circle_ptr如果另一个线程释放了对它的最后一个非循环引用,则a可能从另一个线程变为无效.

我对此并不完全确定.可能只有在您已经对对象的销毁进行数据竞争或使用非拥有引用时才会出现这种情况.但我不确定您的设计是否可以线程安全.

毒力因素

这个想法非常具有病毒性.可能发生循环引用的每种其他类型都必须实现此接口.这不是你可以放在一种类型上的东西.为了获得好处,每种可以参与循环参考的类型都必须使用它.始终如一.

如果你试图circle_ptr要求它管理的对象实现cycle_detector_mixin,那么你就不可能使用任何其他类型的指针.它不会取代(或增加)shared_ptr.因此,编译器无法帮助检测意外误用.

当然,make_shared_from_this编译器无法检测到意外误用.然而,这不是病毒构建体.因此,对于需要此功能的人来说,这只是一个问题.相比之下,获得收益的唯一方法cycle_detector_mixin是尽可能全面地使用它.

同样重要的是,因为这个想法是如此具有病毒性,你将会使用它.因此,您比用户更容易遇到多重继承问题make_shared_from_this.这不是一个小问题.特别是因为cycle_detector_mixin可能会static_cast用来访问派生类,所以你将无法使用虚拟继承.

合计

因此,为了检测周期,你必须做的就是你必须做的事情,编译器将不会验证这些周期:

    参与一个周期的每个班级都必须来自cycle_detector_mixin.

    任何时候,cycle_detector_mixin派生的类circle_ptr在其自身内构造一个实例(直接或间接,但不是在自己派生的类中cycle_detector_mixin),将指针传递给自己cycle_ptr.

    不要假设cycle_ptr类的任何子对象是有效的.由于线程问题,可能甚至在成员函数中变得无效的程度.

这是成本:

    循环检测数据结构cycle_detector_mixin.

    每个都cycle_ptr必须大50%,甚至是不用于循环检测的那些.

对所有权的误解

最终,我认为这个想法归结为对shared_ptr实际情况的误解.

"循环探测器是不必要的,因为循环不是那么频繁,可以很容易地避免使用std::weak_ptr." 事实上,循环很容易在许多简单的数据结构中出现 - 例如,一个树,其中孩子有一个指向父项的后向指针,或一个双向链表.在某些情况下,复杂系统中的异质对象之间的循环仅偶尔与某些数据模式形成,并且难以预测和避免.在某些情况下,用弱变量替换哪个指针远非明显.

这是通用GC的一个非常常见的论点.这个论点的问题在于它通常会假设智能指针的使用无效.

使用shared_ptr 手段的东西.如果类存储a shared_ptr,则表示该类具有该对象的所有权.

所以解释一下:为什么链表中的节点需要拥有下一个节点和前一个节点?为什么树中的子节点需要拥有其父节点?哦,他们需要能够引用其他节点.但他们不需要控制它们的寿命.

例如,我将树节点实现为子节点的数组unique_ptr,并使用指向父节点的单个指针.一个普通的指针,而不是一个智能指针.毕竟,如果树构造正确,父节点将拥有其子节点.因此,如果存在子节点,则它的父节点必须存在 ; 没有有效的父母,孩子就不能存在.

使用双链表,我可能左指针是a unique_ptr,右边是常规指针.或相反亦然; 一种方式并不比另一种更好.

你的心态似乎是我们应该永远使用shared_ptr的东西,让自动系统解决问题的处理方法.无论是循环引用还是其他任何东西,只需让系统搞清楚即可.

这不是shared_ptr为了什么.智能指针的目标并不是你不再考虑所有权; 这是你可以直接在代码中表达所有权关系.

总体

与使用weak_ptr打破周期相比,这有何改进?您现在无处不在,无需识别周期可能发生的时间并进行额外的工作.工作非常脆弱; 如果你做错了,你就不会错过你错过了一个应该用过的地方weak_ptr.只有它更糟糕,因为您可能认为您的代码是安全的.

安全的假象比没有安全更糟糕.至少后者会让你小心.

你能实现这样的东西吗?有可能.它是标准库的合适类型吗?不,它太脆弱了.您必须始终以任何方式正确地实施它,在任何可能出现循环的地方......或者您什么也得不到.

权威参考

对于从未提出,建议或甚至想象用于标准化的事物,可能没有权威参考.升压有没有这样的类型,这样的结构被从未考虑过的boost::shared_ptr.即便是第一个智能指针纸(PDF)也从未考虑过这种可能性.shared_ptr通过一些手动努力扩展到自动处理周期的主题从未在标准提案论坛上进行过讨论,其中已经考虑了很多愚蠢的想法.

我可以提供的最接近参考的是1994年的这篇论文,关于引用计数的智能指针.本文主要谈论使相当于shared_ptrweak_ptr部分语言的(这是在早期的日子,他们甚至不认为这是可以写一个shared_ptr,允许铸造shared_ptrshared_ptr何时U是一个基础T).但即便如此,它还是明确表示不会收集周期.它没有花太多时间在为什么不,但它确实说明了这一点:

但是,具有清理功能的收集对象的循环是有问题的.如果A和B彼此可达,则先破坏任何一个将违反排序保证,留下悬空指针.如果收集器任意中断了循环,程序员将没有真正的排序保证,并且可能导致细微的,与时间相关的错误.到目前为止,还没有人设计出一个安全,通用的解决方案[Hayes 92].

这本质上是我指出的封装/不变问题:使类型的指针成员无效会破坏不变量.

所以基本上,很少有人甚至考虑过这种可能性,那些很快就把它丢弃的人很不切实际.如果你真的相信它们是错的,证明它的唯一最佳方法就是自己实施它.然后提出它用于标准化.



2> Paul Evans..:

std::weak_ptr是解决这个问题的方法.你担心

一个树,其中孩子有一个指向父母的后指针

可以通过使用原始指针作为后向指针来解决.如果你考虑一下,你不用担心泄漏.

而你担心的

双重链表

std::weak_ptr原始的或原始的解决.

推荐阅读
雯颜哥_135
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有